/*
 * -----------------------------------------------------------------------------
 * Project  : ODBC Support
 * Purpose  : Support ODBC connectivity.
 * File     : Win32.Data.Odbc.h
 * Author   : Eduardo Sobrino
 * Date     : Nov/98
 */

#pragma once

#pragma region   // Include Files

#include <windows.h>
#include <sqlucode.h>
#include <sqlext.h>
#include <sqltypes.h>
#include <sqlncli.h>

#include "Win32.Data.DataSource.h"
#include "Win32.Diagnostics.Log.h"
#include "Win32.String.h"
#include "Win32.Types.h"
#include "Win32.MemoryObject.h"
#include "Win32.MemoryItem.h"

#pragma endregion

// -----------------------------------------------------------------------------

namespace Kif
{
namespace Win32
{
namespace Data
{
namespace OdbcClient
{

// -----------------------------------------------------------------------------
#pragma region // General / Common Declarations / Definitions

const int MinStrLen = 256;
const int MaxStrLen = 1028;

typedef SQLWCHAR SqlChar;

enum OdbcErrorNo
{
   OdbcError_SUCCESS = 0,
   OdbcError_CAPABILITY_NOT_SUPPORTED =   8,
   OdbcError_OFEATURE_NOT_IMPLEMENTED =   9,
   OdbcError_FATALERROR               = 990,
   OdbcError_FATALERROR_FAILTOCONNECT = 991,
   OdbcError_FATALERROR_COMMFAILURE   = 992
}  ;

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Declare / Odbc Errors Management Classes

/// <summary>
/// This class iterates though the ODBC error queue and prints all of the
/// accumulated error messages to the console.
/// </summary>

class OdbcErrors
{
private:
   int         m_iLine;    // source code line on which the error occurred
   SQLSMALLINT m_type;     // type of handle on which the error occurred
   SQLHANDLE   m_handle;   // ODBC handle on which the error occurred

public:
   /// <summary>
   ///Default constructor for the ODBCErrors class
   ///</summary>

   OdbcErrors()
   {
      m_iLine  = -1;
      m_type   = 0;
      m_handle = SQL_NULL_HANDLE;
   }

   /// <summary>Constructor for the OdbcErrors class</summary>
   /// <param name="iLine">This parameter is the source code line
   /// at which the error occurred.</param>
   /// <param name="type">This parameter is the type of ODBC handle passed in
   /// the next parameter.</param>
   /// <param name="handle">This parameter is the handle on which the error
   /// occurred.</param>
   OdbcErrors(int iLine, SQLSMALLINT type, SQLHANDLE handle)
   {
      m_iLine  = iLine;
      m_type   = type;
      m_handle = handle;
   }

   Kif::Win32::String *GetErrorString();

}  ;  // end of OdbcErrors

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Declare OdbcDatabase class

class OdbcDatabase
{
private:
public:

   SQLHENV     hEnvironment;                    // environment handle
   SQLHDBC     hDatabaseConnection;             // database connection handle
   SQLHSTMT    hLastStatement;                  // handle to last statement
   SQLRETURN   LastReturnCode;
   SQLSMALLINT DateType;

   long        ConnectionTimeout;               // connection timeout
   long        CommandTimeout;                  // command timeout
   long        DatabaseErrorCode;

   bool        Available;
   bool        Connected;

   Kif::Win32::Diagnostics::Verbosity Verbose;
   Kif::Win32::String ErrorHeaderString;

   void Dispose();
   OdbcDatabase();  // (ctor)
  ~OdbcDatabase()
   {
      Dispose();
   }

   long GetErrorCode(SqlChar *sqlState);
   void GetError(SQLSMALLINT handleType, SQLHANDLE handle);
   bool CheckError(SQLSMALLINT handleType, SQLHANDLE handle);
   int  GetError(Kif::Win32::String &errorMessage);

   inline bool Success()
   {
      return((LastReturnCode == SQL_SUCCESS) ||
             (LastReturnCode == SQL_SUCCESS_WITH_INFO)) ;
   }  ;

}  ;  // end of OdbcDatabase class

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Declare OdbcConnection class

class OdbcConnection
{
private:
public:

   DataSourceInfo DataSource;   
   OdbcDatabase   Database;

   bool Connect(DataSourceInfo &dataSource);
   void Disconnect();

   bool SetConnectionTimeout(long timeout);
   bool SetCommandTimeout(long timeout);
   bool SetCommandTimeout(SQLHSTMT hStatement);

   #pragma region // Error Handling

   inline bool ErrorIsFatal()
   {  return(Database.DatabaseErrorCode >= OdbcError_FATALERROR); }
   inline bool ErrorIsInCommunicationLink()
   {  return(Database.DatabaseErrorCode == OdbcError_FATALERROR_COMMFAILURE); }
   inline long GetError()
   {  return(Database.DatabaseErrorCode); }
   inline long CompareError(long error)
   {  return(Database.DatabaseErrorCode == error); }

   #pragma endregion
   #pragma region // Transaction Management

   inline bool StartTransaction()
   {
      Database.LastReturnCode =
         SQLSetConnectAttr(
            Database.hDatabaseConnection,SQL_ATTR_AUTOCOMMIT,SQL_AUTOCOMMIT_OFF,0);
      return(Database.Success());
   }

   inline bool EndTransaction()
   {
      Database.LastReturnCode = SQLSetConnectAttr(
         Database.hDatabaseConnection,SQL_ATTR_AUTOCOMMIT,
         (void*)SQL_AUTOCOMMIT_ON,0);
      return(Database.Success());
   }

   inline bool CommitAll()
   {  Database.LastReturnCode =
         SQLEndTran(SQL_HANDLE_DBC,Database.hDatabaseConnection,SQL_COMMIT);
      if (Database.Success())
         EndTransaction();
      return(Database.Success());
   }

   inline bool RollbackAll()
   {  Database.LastReturnCode =
         SQLEndTran(SQL_HANDLE_DBC,Database.hDatabaseConnection,SQL_ROLLBACK);
      if (Database.Success())
         EndTransaction();
      return(Database.Success());
   }

   #pragma endregion

}  ;  // end of OdbcConnection class

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Declare Parameters/Columns Collections classes

typedef MemoryDataItem OdbcParameter;
typedef MemoryDataItem OdbcColumn;

typedef MemoryDataItemsCollection OdbcParametersCollection;
typedef MemoryDataItemsCollection OdbcColumnsCollection;

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Declare OdbcCommand class

class OdbcDataReader;
class OdbcCommand
{
private:
   SQLHSTMT hStatement;
   bool GetNewStatementHandle();
   bool SendLargeData();

public:

   inline void Dispose()
   {
      if (hStatement != SQL_NULL_HSTMT)
         SQLFreeStmt(hStatement,SQL_DROP);
      hStatement = SQL_NULL_HSTMT;
   }

   inline SQLHSTMT GetStatementHandle()
   {
      return(hStatement);
   }

   OdbcCommand()
   {
      hStatement = SQL_NULL_HSTMT;
   }

  ~OdbcCommand()
   {
      Dispose();
   }

   Kif::Win32::Data::CommandType CommandType;
   Kif::Win32::String CommandText;

   OdbcConnection *Connection;
   OdbcParametersCollection Parameters;
   OdbcColumnsCollection Columns;

   OdbcCommand(OdbcConnection *connection,TCHAR *commandText,
      Kif::Win32::Data::CommandType commandType)
   {
      hStatement  = SQL_NULL_HSTMT;
      Connection  = connection;
      CommandText.Copy(commandText);
      CommandType = commandType;
   }  // end of OdbcCommand (ctor)

   OdbcCommand(OdbcConnection *connection, Kif::Win32::String &commandText,
      Kif::Win32::Data::CommandType commandType)
   {
      hStatement  = SQL_NULL_HSTMT;
      Connection  = connection;
      CommandText.Copy(commandText,commandText.GetLength());
      CommandType = commandType;
   }  // end of OdbcCommand (ctor)

   bool BindParameters(
      SQLHSTMT hStatement, OdbcParametersCollection &parameters);
   bool BindColumns(
      SQLHSTMT hStatement, OdbcColumnsCollection &columns);

   bool ExecuteNonQuery(Kif::Win32::String &commandText);
   bool ExecuteNonQuery()
   {
      return(ExecuteNonQuery(CommandText));
   }

   bool ExecutePreparedCommand();

   bool Prepare(TCHAR *commandText);
   bool Prepare(TCHAR *commandText, OdbcColumnsCollection &columns);

   OdbcDataReader *OpenReader(
      TCHAR *commandText, OdbcColumnsCollection &columns);
   OdbcDataReader *OpenReader(
      TCHAR *commandText,
      OdbcParametersCollection &params, OdbcColumnsCollection &columns);
   OdbcDataReader *OpenReader()
   {
      return(OpenReader(CommandText.GetBuffer(),Parameters,Columns));
   }

}  ;  // end of OdbcCommand

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Declare OdbcDataReader class

class OdbcDataReader
{
private:
   OdbcCommand *command;
   bool bof, eof;

public:

   OdbcDataReader(OdbcCommand *command)
   {
      this->command = command;
      bof = true;
      eof = false;
   }

   void Close()
   {
      command->Dispose();
   }

   inline void SetLastReturnCode(SQLRETURN code)
   {
      command->Connection->Database.LastReturnCode = code;
   }

   inline SQLRETURN GetLastReturnCode()
   {
      return(command->Connection->Database.LastReturnCode);
   }

   inline bool Success()
   {
      return(command->Connection->Database.Success());
   }

   inline void GetError(SQLSMALLINT handleType, SQLHANDLE handle)
   {
      command->Connection->Database.GetError(handleType,handle);
   }

   bool Read();

}  ; // end of OdbcDataReader

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Declare OdbcFilestream class

#define COPYBUFFERSIZE 4096

#ifdef ODBC_FILESTREAM_SUPPORT_

/// <summary>Helper class to manage MS-SQL Filestream Blobs.</summary>
/// <remarks>Note that to be able to work with this Blobs you need a
/// transaction token as enforced in the class constructure (ctor).</remarks>
class OdbcBlobFile
{
private:
   HANDLE m_FileHandle;
   LPBYTE m_TransactionToken;
   SQLINTEGER m_TransactionTokenSize;

public:

   static const unsigned int DESIRED_ACCESS_READ      = SQL_FILESTREAM_READ;
   static const unsigned int DESIRED_ACCESS_WRITE     = SQL_FILESTREAM_WRITE;
   static const unsigned int DESIRED_ACCESS_READWRITE = SQL_FILESTREAM_READWRITE;

   /// <summary>Passing the transaction is a requirement for Filestreaming (ctor)
   /// </summary>
   /// <param name="transactionToken">transaction token</param>
   /// <param name="transactionTokenSize">transaction token size</param>
   OdbcBlobFile(LPBYTE transactionToken, SQLINTEGER transactionTokenSize)
   {
      m_FileHandle = INVALID_HANDLE_VALUE;
      m_TransactionToken = transactionToken;
      m_TransactionTokenSize = transactionTokenSize;
   }  // end of OdbcBlobFile (ctor)

   BOOL Open(TCHAR *filePath,
      SQL_FILESTREAM_DESIRED_ACCESS desiredAccess,
      Kif::Win32::String *errorMessages);

   inline BOOL OpenRead(TCHAR *filePath, Kif::Win32::String *errorMessages)
   {
      return Open(filePath, SQL_FILESTREAM_READ, errorMessages);
   }  // end of OpenRead

   inline BOOL OpenRead(TCHAR *filePath)
   {
      return Open(filePath, SQL_FILESTREAM_READ, NULL);
   }  // end of OpenRead

   inline BOOL OpenWrite(TCHAR *filePath, Kif::Win32::String *errorMessages)
   {
      return Open(filePath, SQL_FILESTREAM_WRITE, errorMessages);
   }  // end of OpenWrite

   inline BOOL OpenWrite(TCHAR *filePath)
   {
      return Open(filePath, SQL_FILESTREAM_WRITE, NULL);
   }  // end of OpenRead

   DWORD FromFileToBlob(TCHAR *sourceFilePath, TCHAR *destinationFilePath,
      Kif::Win32::String *errorMessages);

   inline BOOL FromFileToBlob(LPTSTR sourceFilePath, LPTSTR destinationFilePath,
      Kif::Win32::String &errorMessages)
   {
      return FromFileToBlob(sourceFilePath, destinationFilePath, &errorMessages);
   }

   DWORD ToFile(TCHAR *filePath, BYTE *buffer, size_t bufferLength,
      Kif::Win32::String *errorMessages);

   /// <summary>Write given buffer to Blob file.</summary>
   /// <param name="buffer">buffer to write</param>
   /// <param name="bufferLength">buffer length</param>
   /// <param name="errorMessages">error messages found (if any) and if an
   /// instance of String is provided, else caller will be notified that
   /// something went wrong by returning -1</param>
   /// <returns>the number of bytes written is returned else -1 if an error
   /// was found</returns>
   inline DWORD Write(BYTE *buffer, size_t bufferLength,
      Kif::Win32::String *errorMessages)
   {
      DWORD bytesWritten = 0;
  		if (WriteFile(m_FileHandle, buffer, bufferLength,
         &bytesWritten, NULL) == 0)
      {
         TCHAR szErrMsgWrite[] = TEXT("Error writing SQL file.");
         if (errorMessages)
         {
            errorMessages->Copy(szErrMsgWrite,SizeOf(szErrMsgWrite));
            bytesWritten = -1;
         }
      }
      return bytesWritten;
   }  // end of Write

   /// <summary>Write given buffer to Blob file.</summary>
   /// <param name="buffer">buffer to write</param>
   /// <param name="bufferLength">buffer length</param>
   /// <returns>the number of bytes written is returned else -1 if an error
   /// was found</returns>
   inline DWORD Write(BYTE *buffer, size_t bufferLength)
   {
      return Write(buffer, bufferLength, NULL);
   }

   DWORD FromBlobToFile(
      TCHAR *sourceFilePath, TCHAR *destinationFilePath,
      Kif::Win32::String *errorMessages);

   inline DWORD FromBlobToFile(TCHAR *sourceFilePath, TCHAR *destinationFilePath)
   {
      return FromBlobToFile(sourceFilePath, destinationFilePath, NULL);
   }

}  ;  // end of OdbcBlobFile class

#endif

#pragma endregion
// -----------------------------------------------------------------------------

}  // end of Kif::Win32::Data::OdbcClient
}  // end of Kif::Win32::Data
}  // end of Kif::Win32
}  // end of Kif

